/*
 * Copyright 2011 Karl Pauls karlpauls@gmail.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package de.kalpatec.pojosr.framework.felix.framework;

import java.util.*;

import org.osgi.framework.*;

import de.kalpatec.pojosr.framework.felix.framework.capabilityset.Attribute;
import de.kalpatec.pojosr.framework.felix.framework.capabilityset.Capability;
import de.kalpatec.pojosr.framework.felix.framework.capabilityset.Directive;
import de.kalpatec.pojosr.framework.felix.framework.util.MapToDictionary;
import de.kalpatec.pojosr.framework.felix.framework.util.StringMap;
import de.kalpatec.pojosr.framework.felix.framework.util.Util;
import org.osgi.framework.wiring.BundleCapability;
import org.osgi.framework.wiring.BundleRevision;

class ServiceRegistrationImpl implements ServiceRegistration
{
    // Service registry.
    private final ServiceRegistry m_registry;
    // Bundle providing the service.
    private final Bundle m_bundle;
    // Interfaces associated with the service object.
    private final String[] m_classes;
    // Service Id associated with the service object.
    private final Long m_serviceId;
    // Service object.
    private volatile Object m_svcObj;
    // Service factory interface.
    private volatile ServiceFactory m_factory;
    // Associated property dictionary.
    private volatile Map m_propMap = new StringMap(false);
    // Re-usable service reference.
    private final ServiceReferenceImpl m_ref;
    // Flag indicating that we are unregistering.
    private volatile boolean m_isUnregistering = false;

    public ServiceRegistrationImpl(ServiceRegistry registry, Bundle bundle,
            String[] classes, Long serviceId, Object svcObj, Dictionary dict)
    {
        m_registry = registry;
        m_bundle = bundle;
        m_classes = classes;
        m_serviceId = serviceId;
        m_svcObj = svcObj;
        m_factory = (m_svcObj instanceof ServiceFactory) ? (ServiceFactory) m_svcObj
                : null;

        initializeProperties(dict);

        // This reference is the "standard" reference for this
        // service and will always be returned by getReference().
        m_ref = new ServiceReferenceImpl();
    }

    protected synchronized boolean isValid()
    {
        return (m_svcObj != null);
    }

    protected synchronized void invalidate()
    {
        m_svcObj = null;
    }

    public synchronized ServiceReference getReference()
    {
        // Make sure registration is valid.
        if (!isValid())
        {
            throw new IllegalStateException(
                    "The service registration is no longer valid.");
        }
        return m_ref;
    }

    public void setProperties(Dictionary dict)
    {
        Map oldProps;
        synchronized (this)
        {
            // Make sure registration is valid.
            if (!isValid())
            {
                throw new IllegalStateException(
                        "The service registration is no longer valid.");
            }
            // Remember old properties.
            oldProps = m_propMap;
            // Set the properties.
            initializeProperties(dict);
        }
        // Tell registry about it.
        m_registry.servicePropertiesModified(this,
                new MapToDictionary(oldProps));
    }

    public void unregister()
    {
        synchronized (this)
        {
            if (!isValid() || m_isUnregistering)
            {
                throw new IllegalStateException("Service already unregistered.");
            }
            m_isUnregistering = true;
        }
        m_registry.unregisterService(m_bundle, this);
        synchronized (this)
        {
            m_svcObj = null;
            m_factory = null;
        }
    }

    //
    // Utility methods.
    //
    Object getProperty(String key)
    {
        return m_propMap.get(key);
    }

    private String[] getPropertyKeys()
    {
        Set s = m_propMap.keySet();
        return (String[]) s.toArray(new String[s.size()]);
    }

    private Bundle[] getUsingBundles()
    {
        return m_registry.getUsingBundles(m_ref);
    }

    /**
     * This method provides direct access to the associated service object; it
     * generally should not be used by anyone other than the service registry
     * itself.
     *
     * @return The service object associated with the registration.
     **/
    Object getService()
    {
        return m_svcObj;
    }

    Object getService(Bundle acqBundle)
    {
        // If the service object is a service factory, then
        // let it create the service object.
        if (m_factory != null)
        {
            Object svcObj = null;
            svcObj = getFactoryUnchecked(acqBundle);

            return svcObj;
        }
        else
        {
            return m_svcObj;
        }
    }

    void ungetService(Bundle relBundle, Object svcObj)
    {
        // If the service object is a service factory, then
        // let it release the service object.
        if (m_factory != null)
        {
            try
            {
                ungetFactoryUnchecked(relBundle, svcObj);
            }
            catch (Exception ex)
            {
                ex.printStackTrace();
            }
        }
    }

    private void initializeProperties(Dictionary dict)
    {
        // Create a case-insensitive map for the properties.
        Map props = new StringMap<Object>(false);

        if (dict != null)
        {
            // Make sure there are no duplicate keys.
            Enumeration keys = dict.keys();
            while (keys.hasMoreElements())
            {
                Object key = keys.nextElement();
                if (props.get(key) == null)
                {
                    props.put(key, dict.get(key));
                }
                else
                {
                    throw new IllegalArgumentException(
                            "Duplicate service property: " + key);
                }
            }
        }

        // Add the framework assigned properties.
        props.put(Constants.OBJECTCLASS, m_classes);
        props.put(Constants.SERVICE_ID, m_serviceId);

        // Update the service property map.
        m_propMap = props;
    }

    private Object getFactoryUnchecked(Bundle bundle)
    {
        Object svcObj = null;
        try
        {
            svcObj = m_factory.getService(bundle, this);
        }
        catch (Throwable th)
        {
            throw new ServiceException("Service factory exception: "
                    + th.getMessage(), ServiceException.FACTORY_EXCEPTION, th);
        }
        if (svcObj != null)
        {
            for (int i = 0; i < m_classes.length; i++)
            {
                try {
                if (!Class.forName(m_classes[i]).isAssignableFrom(svcObj.getClass()))
                {
                    throw new ServiceException(
                            "Service cannot be cast: " + m_classes[i],
                            ServiceException.FACTORY_ERROR);
                }
				} catch (ClassNotFoundException ex) {
				   throw new ServiceException("Service is missing class: " + m_classes[i], ServiceException.FACTORY_ERROR);
				}

            }
        }
        else
        {
            throw new ServiceException("Service factory returned null.",
                    ServiceException.FACTORY_ERROR);
        }
        return svcObj;
    }

    private void ungetFactoryUnchecked(Bundle bundle, Object svcObj)
    {
        m_factory.ungetService(bundle, this, svcObj);
    }



    //
    // ServiceReference implementation
    //

    class ServiceReferenceImpl implements ServiceReference, BundleCapability
    {
        private final ServiceReferenceMap m_map;

        private ServiceReferenceImpl()
        {
            m_map = new ServiceReferenceMap();
        }

        ServiceRegistrationImpl getRegistration()
        {
            return ServiceRegistrationImpl.this;
        }

        //
        // Capability methods.
        //

        @Override
        public BundleRevision getRevision()
        {
            throw new UnsupportedOperationException("Not supported yet.");
        }

        @Override
        public String getNamespace()
        {
            return "service-reference";
        }

        @Override
        public Map<String, String> getDirectives()
        {
            return Collections.EMPTY_MAP;
        }

        @Override
        public Map<String,Object> getAttributes()
        {
            return m_map;
        }

        public List<String> getUses()
        {
            return Collections.EMPTY_LIST;
        }

        public Object getProperty(String s)
        {
            return ServiceRegistrationImpl.this.getProperty(s);
        }

        public String[] getPropertyKeys()
        {
            return ServiceRegistrationImpl.this.getPropertyKeys();
        }

        public Bundle getBundle()
        {
            // The spec says that this should return null if
            // the service is unregistered.
            return (isValid()) ? m_bundle : null;
        }

        public Bundle[] getUsingBundles()
        {
            return ServiceRegistrationImpl.this.getUsingBundles();
        }

        public String toString()
        {
            String[] ocs = (String[]) getProperty("objectClass");
            String oc = "[";
            for (int i = 0; i < ocs.length; i++)
            {
                oc = oc + ocs[i];
                if (i < ocs.length - 1)
                    oc = oc + ", ";
            }
            oc = oc + "]";
            return oc;
        }

        public boolean isAssignableTo(Bundle requester, String className)
        {
            // Always return true if the requester is the same as the provider.
            if (requester == m_bundle)
            {
                return true;
            }

            // Boolean flag.
            boolean allow = true;
            // Get the package.
            String pkgName = Util.getClassPackage(className);
            /*
             * Module requesterModule = ((BundleImpl)
             * requester).getCurrentModule(); // Get package wiring from service
             * requester. Wire requesterWire = Util.getWire(requesterModule,
             * pkgName); // Get package wiring from service provider. Module
             * providerModule = ((BundleImpl) m_bundle).getCurrentModule(); Wire
             * providerWire = Util.getWire(providerModule, pkgName);
             *
             * // There are four situations that may occur here: // 1. Neither
             * the requester, nor provider have wires for the package. // 2. The
             * requester does not have a wire for the package. // 3. The
             * provider does not have a wire for the package. // 4. Both the
             * requester and provider have a wire for the package. // For case
             * 1, if the requester does not have access to the class at // all,
             * we assume it is using reflection and do not filter. If the //
             * requester does have access to the class, then we make sure it is
             * // the same class as the service. For case 2, we do not filter if
             * the // requester is the exporter of the package to which the
             * provider of // the service is wired. Otherwise, as in case 1, if
             * the requester // does not have access to the class at all, we do
             * not filter, but if // it does have access we check if it is the
             * same class accessible to // the providing module. For case 3, the
             * provider will not have a wire // if it is exporting the package,
             * so we determine if the requester // is wired to it or somehow
             * using the same class. For case 4, we // simply compare the
             * exporting modules from the package wiring to // determine if we
             * need to filter the service reference.
             *
             * // Case 1: Both requester and provider have no wire. if
             * ((requesterWire == null) && (providerWire == null)) { // If
             * requester has no access then true, otherwise service //
             * registration must have same class as requester. try { Class
             * requestClass = requesterModule.getClassByDelegation(className);
             * allow = getRegistration().isClassAccessible(requestClass); }
             * catch (Exception ex) { // Requester has no access to the class,
             * so allow it, since // we assume the requester is using
             * reflection. allow = true; } } // Case 2: Requester has no wire,
             * but provider does. else if ((requesterWire == null) &&
             * (providerWire != null)) { // Allow if the requester is the
             * exporter of the provider's wire. if
             * (providerWire.getExporter().equals(requesterModule)) { allow =
             * true; } // Otherwise, check if the requester has access to the
             * class and, // if so, if it is the same class as the provider.
             * else { try { // Try to load class from requester. Class
             * requestClass = requesterModule.getClassByDelegation(className);
             * try { // If requester has access to the class, verify it is the
             * // same class as the provider. allow =
             * (providerWire.getClass(className) == requestClass); } catch
             * (Exception ex) { allow = false; } } catch (Exception ex) { //
             * Requester has no access to the class, so allow it, since // we
             * assume the requester is using reflection. allow = true; } } } //
             * Case 3: Requester has a wire, but provider does not. else if
             * ((requesterWire != null) && (providerWire == null)) { // If the
             * provider is the exporter of the requester's package, then check
             * // if the requester is wired to the latest version of the
             * provider, if so // then allow else don't (the provider has been
             * updated but not refreshed). if (((BundleImpl)
             * m_bundle).hasModule(requesterWire.getExporter())) { allow =
             * providerModule.equals(requesterWire.getExporter()); } // If the
             * provider is not the exporter of the requester's package, // then
             * try to use the service registration to see if the requester's //
             * class is accessible. else { try { // Load the class from the
             * requesting bundle. Class requestClass =
             * requesterModule.getClassByDelegation(className); // Get the
             * service registration and ask it to check // if the service object
             * is assignable to the requesting // bundle's class. allow =
             * getRegistration().isClassAccessible(requestClass); } catch
             * (Exception ex) { // Filter to be safe. allow = false; } } } //
             * Case 4: Both requester and provider have a wire. else { //
             * Include service reference if the wires have the // same source
             * module. allow =
             * providerWire.getExporter().equals(requesterWire.getExporter()); }
             */

            return allow;
        }

        public int compareTo(Object reference)
        {
            ServiceReference other = (ServiceReference) reference;

            Long id = (Long) getProperty(Constants.SERVICE_ID);
            Long otherId = (Long) other.getProperty(Constants.SERVICE_ID);

            if (id.equals(otherId))
            {
                return 0; // same service
            }

            Object rankObj = getProperty(Constants.SERVICE_RANKING);
            Object otherRankObj = other.getProperty(Constants.SERVICE_RANKING);

            // If no rank, then spec says it defaults to zero.
            rankObj = (rankObj == null) ? new Integer(0) : rankObj;
            otherRankObj = (otherRankObj == null) ? new Integer(0)
                    : otherRankObj;

            // If rank is not Integer, then spec says it defaults to zero.
            Integer rank = (rankObj instanceof Integer) ? (Integer) rankObj
                    : new Integer(0);
            Integer otherRank = (otherRankObj instanceof Integer) ? (Integer) otherRankObj
                    : new Integer(0);

            // Sort by rank in ascending order.
            if (rank.compareTo(otherRank) < 0)
            {
                return -1; // lower rank
            }
            else if (rank.compareTo(otherRank) > 0)
            {
                return 1; // higher rank
            }

            // If ranks are equal, then sort by service id in descending order.
            return (id.compareTo(otherId) < 0) ? 1 : -1;
        }
    }

     private class ServiceReferenceMap implements Map
    {
        public int size()
        {
            throw new UnsupportedOperationException("Not supported yet.");
        }

        public boolean isEmpty()
        {
            throw new UnsupportedOperationException("Not supported yet.");
        }

        public boolean containsKey(Object o)
        {
            throw new UnsupportedOperationException("Not supported yet.");
        }

        public boolean containsValue(Object o)
        {
            throw new UnsupportedOperationException("Not supported yet.");
        }

        public Object get(Object o)
        {
            return ServiceRegistrationImpl.this.getProperty((String) o);
        }

        public Object put(Object k, Object v)
        {
            throw new UnsupportedOperationException("Not supported yet.");
        }

        public Object remove(Object o)
        {
            throw new UnsupportedOperationException("Not supported yet.");
        }

        public void putAll(Map map)
        {
            throw new UnsupportedOperationException("Not supported yet.");
        }

        public void clear()
        {
            throw new UnsupportedOperationException("Not supported yet.");
        }

        public Set<Object> keySet()
        {
            throw new UnsupportedOperationException("Not supported yet.");
        }

        public Collection<Object> values()
        {
            throw new UnsupportedOperationException("Not supported yet.");
        }

        public Set<Entry<Object, Object>> entrySet()
        {
            return Collections.EMPTY_SET;
        }
    }
}